package com.wsm.gateway.filter;

import com.wsm.common.utils.StringUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

/**
 * @author ：wangshimin
 * @date ：Created in 2020-11-02 下午 02:24
 * @description：
 * @version:
 */
@Component
@Slf4j
@AllArgsConstructor
public class HttpRequestFilter implements GlobalFilter, Ordered {

    private static final String START_TIME = "startTime";

    private static final String TRACE_ID = "traceId";

    private static final String POST = "POST";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String method = request.getMethodValue();
        if (POST.equals(method)) {
            return DataBufferUtils.join(exchange.getRequest().getBody())
                    .flatMap(dataBuffer -> {
                        byte[] bytes = new byte[dataBuffer.readableByteCount()];
                        dataBuffer.read(bytes);
                        try {
                            saveLogs(exchange, request, method, new String(bytes, "utf-8"));
                        } catch (UnsupportedEncodingException e) {
                            e.printStackTrace();
                        }
                        DataBufferUtils.release(dataBuffer);
                        Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
                            DataBuffer buffer = exchange.getResponse().bufferFactory()
                                    .wrap(bytes);
                            return Mono.just(buffer);
                        });
                        ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(
                                exchange.getRequest()) {
                            @Override
                            public Flux<DataBuffer> getBody() {
                                return cachedFlux;
                            }
                        };
                        return chain.filter(exchange.mutate().request(mutatedRequest)
                                .build());
                    });
        } else {
            saveLogs(exchange, request, method, null);
        }
        return chain.filter(exchange);
    }

    /**
     * @return: void
     * @author: wangshimin
     * @date: 2020-11-02 下午 02:45
     * @param: exchange
     * @param: request
     * @param: method
     * @param: queryParams
     * @description: 打印日志
     * @version:
     */
    private void saveLogs(ServerWebExchange exchange, ServerHttpRequest request, String method, String queryParams) {
        String requestUrl = request.getURI().getRawPath();
        String traceId = request.getHeaders().getFirst("trace-id");
        StringBuilder reqBuilder = new StringBuilder(300);
        List<Object> reqArgs = new ArrayList<>();
        reqBuilder.append("request ===> Method:{} Host:{} Path:{} Query:{} TraceId:{}");
        reqArgs.add(method);
        reqArgs.add(request.getURI().getHost());
        reqArgs.add(requestUrl);
        reqArgs.add(StringUtils.isEmpty(queryParams) ? request.getQueryParams() : queryParams);
        reqArgs.add(traceId);
        exchange.getAttributes().put(START_TIME, System.currentTimeMillis());
        exchange.getAttributes().put(TRACE_ID, traceId);
        log.info(reqBuilder.toString(), reqArgs.toArray());
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}